/*
 * Copyright 2011 Jin Kwon <jinahya at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.googlecode.flac4j.format.metadata;


import com.googlecode.flac4j.format.FormatHelper;

import com.googlecode.jinahya.io.BitInput;
import com.googlecode.jinahya.io.BitOutput;

import java.io.IOException;

import java.util.Arrays;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;


/**
 *
 * @author Jin Kwon <jinahya at gmail.com>
 */
@XmlType(propOrder = {"minimumBlockSize", "maximumBlockSize",
                      "minimumFrameSize", "maximumFrameSize", "sampleRate",
                      "channelCount", "sampleSize", "sampleCount", "signature"})
public class StreamInfo extends AbstractData {


    /** specified minimumBlockSize. */
    public static final int MINIMUM_BLOCK_SIZE = 16;


    /** specified maximumBlockSize. */
    public static final int MAXIMUM_BLOCK_SIZE = 65535;


    /** minimum bitsPerSample. */
    public static final int MINIMUM_SAMPLE_SIZE = 4;


    /** maximum bitsPerSample. */
    public static final int MAXIMUM_SAMPLE_SIZE = 32;


    /** maximum sampleRate. */
    public static final int MAXIMUM_SAMPLE_RATE = 655350;


    private static final int LENGTH_IN_BYTES = 34;


    /**
     * Creates a new instance.
     */
    public StreamInfo() {
        super(BlockType.STREAMINFO);
    }


    @Override
    public int getLengthInBytes() {
        return LENGTH_IN_BYTES;
    }


    @Override
    public void read(final BitInput input) throws IOException {

        setMinimumBlockSize(input.readUnsignedInt(16));
        setMaximumBlockSize(input.readUnsignedInt(16));

        setMinimumFrameSize(input.readUnsignedInt(24));
        setMaximumFrameSize(input.readUnsignedInt(24));

        setSampleRate(input.readUnsignedInt(20));

        setChannelCount(input.readUnsignedInt(3));

        setSampleSize(input.readUnsignedInt(5));

        setSampleCount(input.readUnsignedLong(36));

        signature = new byte[16];
        FormatHelper.readBytes(input, signature);
    }


    @Override
    public void write(final BitOutput output) throws IOException {

        output.writeUnsignedInt(16, getMinimumBlockSize());
        output.writeUnsignedInt(16, getMaximumBlockSize());

        output.writeUnsignedInt(24, getMinimumFrameSize());
        output.writeUnsignedInt(24, getMaximumFrameSize());

        output.writeUnsignedInt(20, getSampleRate());

        output.writeUnsignedInt(3, getChannelCount());

        output.writeUnsignedInt(5, getSampleSize());

        output.writeUnsignedLong(36, getSampleCount());

        FormatHelper.writeBytes(output, signature);
    }


    // -------------------------------------------------------- minimumBlockSize
    public int getMinimumBlockSize() {
        return minimumBlockSize;
    }


    public void setMinimumBlockSize(final int minimumBlockSize) {

        if (maximumBlockSize < 0) {
            throw new IllegalArgumentException(
                "minimumBlockSize(" + minimumBlockSize + ") < 0");
        }

        this.minimumBlockSize = minimumBlockSize;
    }


    // -------------------------------------------------------- maximumBlockSize
    /**
     * Returns maximumBlockSize.
     *
     * @return maximumBlockSize
     */
    public int getMaximumBlockSize() {
        return maximumBlockSize;
    }


    /**
     * Sets maximumBlockSize.
     *
     * @param maximumBlockSize maximumBlockSize
     */
    public void setMaximumBlockSize(final int maximumBlockSize) {

        if (maximumBlockSize < 0) {
            throw new IllegalArgumentException(
                "maximumBlockSize(" + maximumBlockSize + ") < 0");
        }

        this.maximumBlockSize = maximumBlockSize;
    }


    // -------------------------------------------------------- minimumFrameSize
    /**
     * Returns minimumFrameSize.
     *
     * @return minimumFrameSize
     */
    public int getMinimumFrameSize() {
        return minimumFrameSize;
    }


    /**
     * Sets minimumFrameSize.
     *
     * @param minimumFrameSize minimumFrameSize
     */
    public void setMinimumFrameSize(final int minimumFrameSize) {

        if (maximumBlockSize < 0) {
            throw new IllegalArgumentException(
                "minimumFrameSize(" + minimumFrameSize + ") < 0");
        }

        this.minimumFrameSize = minimumFrameSize;
    }


    // -------------------------------------------------------- maximumFrameSize
    /**
     * Returns maximumFrameSize.
     *
     * @return maximumFrameSize
     */
    public int getMaximumFrameSize() {
        return maximumFrameSize;
    }


    /**
     * Sets maximumFrameSize.
     *
     * @param maximumFrameSize maximumFrameSize
     */
    public void setMaximumFrameSize(final int maximumFrameSize) {

        if (maximumBlockSize < 0) {
            throw new IllegalArgumentException(
                "maximumFrameSize(" + maximumFrameSize + ") < 0");
        }

        this.maximumFrameSize = maximumFrameSize;
    }


    // -------------------------------------------------------------- sampleRate
    /**
     * Returns sampleRate.
     *
     * @return sampleRate.
     */
    public int getSampleRate() {
        return sampleRate;
    }


    /**
     * Sets sampleRate.
     *
     * @param sampleRate sampleRate
     */
    public void setSampleRate(final int sampleRate) {

        if (sampleRate < 0) {
            throw new IllegalArgumentException(
                "sampleRate(" + sampleRate + ") < 0");
        }

        if (sampleRate == 0) {
            throw new IllegalArgumentException(
                "invalid sampleRate: " + sampleRate);
        }

        if (sampleRate > MAXIMUM_SAMPLE_RATE) {
            throw new IllegalArgumentException(
                "sampleRate(" + sampleRate + ") > " + MAXIMUM_SAMPLE_RATE);
        }

        this.sampleRate = sampleRate;
    }


    // ------------------------------------------------------------ channelCount
    /**
     * Returns channelCount.
     *
     * @return channelCount
     */
    public int getChannelCount() {
        return channelCount;
    }


    /**
     * Sets channelCount.
     *
     * @param channelCount channelCount
     */
    public void setChannelCount(final int channelCount) {

        if (maximumBlockSize < 0) {
            throw new IllegalArgumentException(
                "channelCount(" + channelCount + ") < 0");
        }

        this.channelCount = channelCount;
    }


    // -------------------------------------------------------------- sampleSize
    /**
     * Returns sampleSize.
     *
     * @return sampleSize
     */
    public int getSampleSize() {
        return sampleSize;
    }


    /**
     * Sets sampleSize.
     *
     * @param sampleSize sampleSize
     */
    public void setSampleSize(final int sampleSize) {

        if (sampleSize < MINIMUM_SAMPLE_SIZE) {
            throw new IllegalArgumentException(
                "sampleSize(" + sampleSize + ") < " + MINIMUM_SAMPLE_SIZE);
        }

        this.sampleSize = sampleSize;
    }


    // ------------------------------------------------------------- sampleCount
    /**
     * Returns sampleCount.
     *
     * @return sampleCount
     */
    public long getSampleCount() {
        return sampleCount;
    }


    /**
     * Sets sampleCount.
     *
     * @param sampleCount 
     */
    public void setSampleCount(long sampleCount) {

        if (sampleCount < 0) {
            throw new IllegalArgumentException(
                "sampleCount(" + sampleCount + ") < 0");
        }

        this.sampleCount = sampleCount;
    }


    // --------------------------------------------------------------- signature
    /**
     * Returns signature.
     *
     * @return signature.
     */
    public byte[] getSignature() {
        return Arrays.copyOf(signature, signature.length);
    }


    /**
     * Sets signature.
     *
     * @param signature signature.
     */
    public void setSignature(final byte[] signature) {

        if (signature == null) {
            throw new NullPointerException("null signaturer");
        }

        if (signature.length != 16) {
            throw new IllegalArgumentException("signature.length != 16");
        }

        this.signature = Arrays.copyOf(signature, signature.length);
    }


    @XmlElement(required = true)
    private int minimumBlockSize = 0;


    @XmlElement(required = true)
    private int maximumBlockSize = 0;


    @XmlElement(required = true)
    private int minimumFrameSize;


    @XmlElement(required = true)
    private int maximumFrameSize;


    @XmlElement(required = true)
    private int sampleRate;


    @XmlElement(required = true)
    private int channelCount;


    @XmlElement(required = true)
    private int sampleSize;


    @XmlElement(required = true)
    private long sampleCount;


    @XmlElement(required = true)
    //@XmlSchemaType(name = "hexBinary")
    @XmlJavaTypeAdapter(HexBinaryAdapter.class)
    private byte[] signature;


}

